package resp

import (
	"encoding/json"
	"fmt"
	"gitee.com/luotian110/lt-go-lib/ltutils"
	"gitee.com/luotian110/lt-go-lib/timehelp"
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"io"
	"net/http"
	"strings"
	"time"
)

var (
	logout = false //是否对外输出底层错误日志
)

type BaseResponse struct {
	Code  int         `json:"code"`
	Data  interface{} `json:"data,omitempty"` //详细数据
	Msg   string      `json:"msg"`
	Time  string      `json:"time"`
	Log   string      `json:"log,omitempty"` //详细错误，开发者日志
	Count int64       `json:"count"`         //数据条数，data为数组时返回
}

const (
	CodeOk            = 200  //成功
	CodeNeedLogin     = 1001 //未登录或者登录过期
	CodeParam         = 1002 //参数错误
	CodeDBError       = 1003 //数据库操作失败
	CodeNoRecord      = 1004 //没有查询到相关记录
	CodeInternalError = 1005 //其他不好指定错误类别的错误
	CodeUnknown       = 1999 //未知错误
)

func SetLogout(b bool) {
	logout = b
}

// Err 通用错误处理
func Err(ctx *gin.Context, errCode int, msg string, err error) {
	res := &BaseResponse{
		Code: errCode,
		Msg:  msg,
		Time: timehelp.GetSimpleTimeNow(),
	}
	if msg == "" {
		res.Msg = err.Error()
	}
	// 生产环境隐藏底层报错
	if err != nil && logout {
		res.Log = err.Error()
	}
	ctx.AbortWithStatusJSON(http.StatusOK, res)
}

func ErrMsg(ctx *gin.Context, msg string) {
	res := &BaseResponse{
		Code: CodeInternalError,
		Msg:  msg,
		Time: timehelp.GetSimpleTimeNow(),
	}
	if msg == "" {
		res.Msg = msg
	}
	ctx.IndentedJSON(http.StatusOK, res)
}

func ErrError(ctx *gin.Context, err error) {
	//处理参数错误
	if ve, ok := err.(validator.ValidationErrors); ok {
		for _, e := range ve {
			tag := "错误"
			if e.Tag() == "required" {
				tag = "必须存在，且不能为空"
			}
			Err(ctx, CodeParam, fmt.Sprintf("参数:%s%s", e.Field(), tag), err)
			return
		}
	}
	//处理gorm未查询到数据
	if errors.Is(err, gorm.ErrRecordNotFound) {
		Err(ctx, CodeNoRecord, "无相关数据", err)
		return
	}
	//处理自定义返回的错误
	for _, v := range err.Error() {
		//如果包含汉字
		if ltutils.IsHanZi(v) {
			Err(ctx, CodeInternalError, err.Error(), err)
			return
		}
	}
	//处理json错误
	if _, ok := err.(*json.UnmarshalTypeError); ok {
		Err(ctx, CodeParam, "JSON类型不匹配", err)
		return
	}
	//未知错误
	Err(ctx, CodeUnknown, "未知错误", err)
}

// Err500 内部错误处理
func Err500(ctx *gin.Context, msg string) {
	Err(ctx, CodeInternalError, msg, nil)
}

func Err403(ctx *gin.Context, msg string) {
	if msg == "" {
		msg = "无访问权限"
	}
	Err(ctx, http.StatusForbidden, msg, nil)
}

func ErrDb(ctx *gin.Context, err error) {
	msg := "数据库操作失败"
	code := CodeDBError
	if err != nil {
		if strings.EqualFold("record not found", err.Error()) {
			msg = "没有查询到相关记录"
			code = CodeNoRecord
		} else if strings.Index(err.Error(), "") >= 0 {
			msg = err.Error()
			code = CodeNoRecord
		}
	}
	Err(ctx, code, msg, err)
}

func ErrNeedLogin(ctx *gin.Context) {
	Err(ctx, CodeNeedLogin, "未登录或者会话过期", nil)
}

// ErrParam 参数错误
func ErrParam(ctx *gin.Context, param string) {
	Err(ctx, CodeParam, fmt.Sprintf("参数%v错误", param), nil)
}

// OkData 请求成功，返回对象类型的数据
func OkData(ctx *gin.Context, data interface{}) {
	res := &BaseResponse{
		Code: CodeOk,
		Msg:  "ok",
		Time: timehelp.GetSimpleTimeNow(),
		Data: data,
	}
	ctx.IndentedJSON(http.StatusOK, res)
}

// OkList 请求成功，返回列表类型的数据
func OkList(ctx *gin.Context, data interface{}, count int64) {
	res := &BaseResponse{
		Code:  CodeOk,
		Msg:   "ok",
		Time:  timehelp.GetSimpleTimeNow(),
		Data:  data,
		Count: count,
	}
	ctx.IndentedJSON(http.StatusOK, res)
}

func OkMsg(ctx *gin.Context, msg string) {
	if msg == "" {
		msg = "ok"
	}
	res := &BaseResponse{
		Code: CodeOk,
		Msg:  msg,
		Time: timehelp.GetSimpleTimeNow(),
		Data: "",
	}
	ctx.IndentedJSON(http.StatusOK, res)
}

// Ok 请求成功，返回对象类型的数据
func Ok(ctx *gin.Context, data interface{}, msg string) {
	res := &BaseResponse{
		Code: CodeOk,
		Msg:  msg,
		Time: timehelp.GetSimpleTimeNow(),
		Data: data,
	}
	ctx.IndentedJSON(http.StatusOK, res)
}

// Xlsx 向前端返回Excel文件
// 参数 content 为上面生成的io.ReadSeeker， fileTag 为返回前端的文件名
func Xlsx(c *gin.Context, content io.ReadSeeker, fileTag string) {
	fileName := fmt.Sprintf("%s.xlsx", fileTag)
	c.Writer.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName))
	c.Writer.Header().Add("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
	http.ServeContent(c.Writer, c.Request, fileName, time.Now(), content)
}
